﻿#include "datamodel.h"
#include "declarationmanager.h"

#include <cstring>
#include <stack>

#include "pugixml/pugixml.hpp"


Node::Node(const std::string &name, bool required, Node *parent)
    :name(name), required(required), parent(parent)
{

}

void Node::Destory() const
{
    delete this;
}

const Node *Node::clone(bool required, Node *new_parent) const
{
    Node *new_node = new Node{this->name, required, new_parent};
    // clone attrs
    for (const auto &attr: this->attrs)
    {
        new_node->attrs.push_back (attr->clone ());
    }

    // clone sub nodes
    for (const auto &sub: this->sub_nodes)
    {
        new_node->sub_nodes.insert (std::make_pair(sub.first, sub.second->clone (required, new_node)));
    }

    // clone addition attrs
    for (const auto &add: this->addition_attrs)
    {
        new_node->addition_attrs.insert (std::make_pair(add.first, add.second->clone ()));
    }

    return new_node;
}

Node::~Node()
{
    // clean up attrs
    for (auto &x: attrs)
        x->Destory();
    //clean up sub_nodes
    for (auto &x: sub_nodes)
        x.second->Destory();
    //clean up addition_attrs
    for (auto &x: addition_attrs)
        x.second->Destory();
}

Attribute::Attribute(const std::string &name, bool required)
    :name(name), required(required)
{

}

void Attribute::Destory() const
{
    delete this;
}

Attribute *Attribute::clone() const
{
    return new Attribute{this->name, this->required};
}

static Attribute *ParseAttribut(const pugi::xml_node &attr_node)
{
    if (!attr_node)
        return nullptr;

    std::string name = attr_node.name();
    bool required = false;
    if (!strcmp("true", attr_node.attribute("required").as_string("true")))
        required = true;

    return new Attribute(name, required);
}

static void SplitString(const std::string &src, const std::string &delim, std::set<std::string> &result)
{
    std::string::size_type current, previous = 0;
    current = src.find(delim, previous);
    while (current != std::string::npos) {
        result.insert(src.substr(previous, current - previous));
        previous = current + 1;
        current = src.find(delim);
    }
    result.insert(src.substr(previous, current - previous));
}

static Attribute *ParseAdditionAttribut(const pugi::xml_node &addition_attr_node, std::set<std::string> &add_to)
{
    if (!addition_attr_node)
        return nullptr;

    std::string target = addition_attr_node.attribute("target").as_string();
    if (target.empty())
        return nullptr;

    ::SplitString(target, "|", add_to);

    return ParseAttribut(addition_attr_node);
}

const Node *BuildNodeTreeFromXml(const pugi::xml_node &root, Node *parent)
{
    using namespace pugi;
    using namespace std;

    if (!root)
        return nullptr;

    // Basic Info name & required
    string name = root.name();
    bool required = false;
    if (!strcmp("true", root.attribute("required").as_string("true")))
        required = true;

    // import type?
    string import_type = root.attribute ("import").as_string ();
    if (!import_type.empty ())
    {
        const Node *type = DeclarationManager::getInstance ().findNodeDeclaration (import_type);
        if (!type)
            throw std::runtime_error("missing type defined: " + import_type);
        return type->clone (required, parent);
    }

    Node *pNode = new Node(name, required, parent);

    // parse attrs
    auto attrs_node = root.child("attrs");
    if (attrs_node)
    {
        Attribute *attr = nullptr;
        for (auto &node: attrs_node.children())
        {
            attr = ParseAttribut(node);
            if (attr)
                pNode->attrs.push_back(attr);
        }
    }

    // addition_attrs
    auto addition_attrs = root.child("addition_attrs");
    if (addition_attrs)
    {
        std::set<std::string> targets;
        Attribute *attr = nullptr;
        for (auto &node: addition_attrs)
        {
            targets.clear();
            attr = ParseAdditionAttribut(node, targets);
            if (attr)
                pNode->addition_attrs.insert(std::make_pair(targets, attr));

        }
    }

    // sub nodes
    auto sub_nodes = root.child("sub_nodes");
    if (sub_nodes)
    {
        const Node *sub = nullptr;
        std::string node_name;
        for (auto &node: sub_nodes)
        {
            node_name = node.name();
            sub = BuildNodeTreeFromXml(node, pNode);
            if (sub)
                pNode->sub_nodes.insert(std::make_pair(node_name, sub));
        }
    }

    return pNode;
}

void TraceError(const Node* node, const pugi::xml_node& xml_node, const std::string& info, std::ostream &out)
{
    if (!node)
        return;

    std::stack<std::string> node_stack;
    while (node) {
        node_stack.push(node->name);
        node = node->parent;
    }

    out << "Error in node xpath=\"" << xml_node.path() << "\"\n\trule#";

    while (!node_stack.empty()) {
        out << node_stack.top();
        if (node_stack.size() > 1)
            out << "=>";
        node_stack.pop();
    }
    out << " : " << info << "\n";
}

void ValidationXmlFileWithRule(const pugi::xml_node &root, const Node *rule, std::ostream &out_stream)
{
    if (!root)
    {
        out_stream << "no such node!\n";
        return;
    }

    if (!rule)
    {
        out_stream << "no such rule!\n";
        return;
    }

//    bool check_children = true;

    const Node *current_rule = rule;
    pugi::xml_node current_node = root;

    // valitation current node
    // node name
    if (current_node.name() != current_rule->name)
    {
        TraceError(current_rule, current_node, "node name is invalid!!", out_stream);
//        check_children = false;
    }

    // attrs, only check requires
    if (current_rule->attrs.size() > 0)
    {
        for (auto &attr: current_rule->attrs)
        {
            if (attr->required && !current_node.attribute(attr->name.c_str()))
            {
                TraceError(current_rule, current_node, ("Required attribute not found @" + attr->name), out_stream);
//                check_children = false;
//                break;
            }
        }
    }

    // has parent? check addition_attrs, requires only
    if (current_rule->parent)
    {
        current_rule = current_rule->parent;

        // get node name
        std::string name = current_node.name();

        for (auto &attr_cfg: current_rule->addition_attrs)
        {
            if (attr_cfg.second->required)
            {
                auto &name_set = attr_cfg.first;
                // equals 1 that means it's '*'
                if (name_set.size () == 1 || name_set.find(name) != name_set.end())
                {
                    if (!current_node.attribute(attr_cfg.second->name.c_str()))
                    {
                        TraceError(rule, current_node, ("Required addition attribute not found @" + attr_cfg.second->name), out_stream);
//                        check_children = false;
//                        break;
                    }
                }
            }

        }
        current_rule = rule;
    }

    // check children node
//    if (check_children)
    {
        std::string child_name;
        auto &sub_nodes = current_rule->sub_nodes;
        for (auto &child: current_node.children())
        {
            child_name = child.name();
            auto itr = sub_nodes.find(child_name);
            if (itr == sub_nodes.end())
            {
                TraceError(current_rule, child, ("The node was not defined in rule: " + child_name), out_stream);
            }
            else
                ValidationXmlFileWithRule(child, itr->second, out_stream);
        }
    }

}
